require 'sinatra/base'

module Sinatra
  
  # Public: Helpers used in the Chassis module.
  # Can be overriden from an app.
  module ChassisHelpers
    
    # Public: Compares the request user agent against
    # the mobile_user_agents setting Array items (should contain regexes).
    # 
    # Example 
    # 
    #   mobile_request?
    #   # => true
    # 
    # Returns true/false.
    def mobile_request?
      settings.mobile_user_agents.any? { |agent| request.env['HTTP_USER_AGENT'] =~ agent }
    end
    
    # Public: Checks to see if a view template exists.
    # It will check through all registered view directories.
    # 
    # template - String of view template filename to look for
    # 
    # Example
    # 
    #   view_template('show.haml')
    #   # => true
    # 
    # Returns true/false.
    def view_exists? template
      Array(settings.views).each { |v| return true if File.exists?("#{v}/#{template}") }
      false
    end
    
    # Public: Prepends the Sinatra find_template method to
    # find .mobile templates if the mobile_views setting is true
    # the request is from a mobile device.
    # 
    # Example
    # 
    #   erb :my_view
    # 
    # Renders the my_view.mobile.erb instead of my_view.erb.
    def find_template views, name, engine, &block
      enable :reload_templates if settings.mobile_views
      name = "#{name}.mobile"   if
        (settings.mobile_views) &&
        (mobile_request?)       &&
        (view_exists?("#{name}.mobile.#{@preferred_extension}"))
      Array(views).each { |v| super(v, name, engine, &block) }
    end
    
  end
  
  module Chassis
    
    # Public: Requires all .rb files in a given directory or directories.
    # 
    # *args - String(s) or Array of String paths to require
    # 
    # Exmaple
    # 
    #   require_directory('routes', 'models')
    # 
    # Returns nothing.
    def require_directory *args
      args = args.first if args.first.kind_of? Array
      args.each do |directory|
        Dir["./#{directory}/**/*.rb"].each { |file| require file }
      end
    end
    
    # Public: Additions to the Sinatra app.
    def self.registered(app)
      
      # Deprecated: Defines the default load path to be used with require_directory.
      # 
      # Example
      # 
      #   require_directory(settings.load_path)
      app.set :load_path, ['config', 'settings', 'modules', 'helpers', 'libraries', 'models', 'controllers', 'routes']
      
      # Public: Defines the default mobile user agents.
      app.set :mobile_user_agents, [/iPhone/, /Android.*AppleWebKit/]
      
      # Public: Turns on .mobile view templates.
      app.disable :mobile_views
      
      # Public: Determines where to load assets from.
      app.set :assets_path, ['public']
      
      # Public: Compiles .scss file to .css on request.
      # 
      # Example
      # 
      #   <link rel="stylesheet" type="text/css" href="theme.css">
      # 
      # Returns the compiled theme.scss, if it exists.
      # Note that requests to public files bypass this handler.
      app.get '*.css/?' do        
        file = params[:splat].first
        
        found = false
        type  = nil
        
        settings.assets_path.each do |path|
          file.sub!("#{path}/", '') unless path == 'public'
          if File.exists?("./#{path}#{file}.css")
            found = "#{path}#{file}"
            type  = 'css'
            break
          end
          if File.exists?("./#{path}#{file}.scss")
            found = "#{path}#{file}"
            type  = 'scss'
            break
          end
        end
        
        if found
          content_type 'text/css'
          if type == 'css'
            File.read "#{found}.css"
          elsif type == 'scss'
            scss found.to_sym, views: './'
          end
        else
          pass
        end
      end
      
      # Public: Compiles .coffee file to .js on request.
      # 
      # Example
      # 
      #   <script type="text/javascript" src"script.js"></script>
      # 
      # Returns the compiled script.coffee, if it exists.
      # Note that requests to public files bypass this handler.
      app.get '*.js/?' do
        file = params[:splat].first
        
        found = false
        type  = nil
        
        settings.assets_path.each do |path|
          file.sub!("#{path}/", '') unless path == 'public'
          if File.exists?("./#{path}#{file}.js")
            found = "#{path}#{file}"
            type  = 'js'
            break
          end
          if File.exists?("./#{path}#{file}.coffee")
            found = "#{path}#{file}"
            type  = 'coffee'
            break
          end
        end
        
        if found
          content_type 'text/javascript'
          if type == 'js'
            File.read "#{found}.js"
          elsif type == 'coffee'
            coffee found.to_sym, views: './'
          end
        else
          pass
        end
      end
      
      # Public: Adds the internal Chassis views directory to the app.
      # Used for finding the built in error template.
      app.set :views, ['views', File.dirname(__FILE__) + '/views']
      
      # Public: Turns on the catch all route in a not_found request.
      app.enable :catch_all_route
      
      # Public: Handles 404s.
      # 
      # If there is no matching route handler, this will return a view template
      # by matching the request path to a /views directory path and file. If
      # no template is found, a 404 is called.
      # 
      # In production, the 404 will render the production error template.
      app.not_found do
        if settings.catch_all_route?
          view = request.path[1..-1]
          Tilt.mappings.each do |m|
            return send(m.first, view.to_sym) if view_exists?("#{view}.#{m.first}")
          end
        end
        erb(:error, layout: false, locals: { code: '404', message: 'Not Found' }) if settings.production?
      end
      
      # Public: Makes production errors prettier by rendering a built in template.
      app.configure :production do
        app.error(400) { erb :error, layout: false, locals: { code: '400', message: 'Bad Request'           } }
        app.error(401) { erb :error, layout: false, locals: { code: '401', message: 'Unauthorized'          } }
        app.error(403) { erb :error, layout: false, locals: { code: '403', message: 'Forbidden'             } }
        app.error(408) { erb :error, layout: false, locals: { code: '408', message: 'Request Timeout'       } }
        app.error(500) { erb :error, layout: false, locals: { code: '500', message: 'Internal Server Error' } }
        app.error(502) { erb :error, layout: false, locals: { code: '502', message: 'Bad Gateway'           } }
      end
    end
    
  end
  
  helpers  ChassisHelpers
  register Chassis
end
